package org.koin.core;

import org.koin.core.annotation.KoinInternalApi;
import org.koin.core.error.DefinitionOverrideException;
import org.koin.core.error.NoScopeDefFoundException;
import org.koin.core.error.ScopeAlreadyCreatedException;
import org.koin.core.logger.Level;
import org.koin.core.logger.Logger;
import org.koin.core.module.Module;
import org.koin.mp.KoinPlatformTools;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;

/**
 * Koin Application
 * Help prepare resources for Koin context
 *
 * @author Arnaud Giuliani
 */
@KoinInternalApi
public class KoinApplication {

    private Koin koin = new Koin();

    public KoinApplication() {

    }

    public static KoinApplication init()
            throws ScopeAlreadyCreatedException,
            NoScopeDefFoundException {
        KoinApplication app = new KoinApplication();
        app._init();
        return app;
    }

    protected void _init() throws ScopeAlreadyCreatedException, NoScopeDefFoundException {
        koin.getScopeRegistry().createRootScopeDefinition();
        koin.getScopeRegistry().createRootScope();
    }

    /**
     * Load definitions from modules
     *
     * @param module
     */
    public KoinApplication modules(Module module) throws DefinitionOverrideException {
        return modules(Arrays.asList(module));
    }

    /**
     * Load definitions from modules
     *
     * @param modules
     */
    public KoinApplication modules(Module... modules) throws DefinitionOverrideException {
        return modules(Arrays.asList(modules));
    }

    /**
     * Load definitions from modules
     *
     * @param modules
     */
    public KoinApplication modules(List<Module> modules) throws DefinitionOverrideException {
        if (koin.getLogger().isAt(Level.INFO)) {

            double start = System.nanoTime() / 1000000.0;
            loadModules(modules);
            double end = System.nanoTime() / 1000000.0;

            int count = koin.getScopeRegistry().size();
            DecimalFormat to = new DecimalFormat("0.0000");

            koin.getLogger().info("loaded "
                    + count
                    + " definitions - "
                    + to.format(end - start)
                    + " ms");
        } else {
            loadModules(modules);
        }
        return this;
    }

    private void loadModules(List<Module> modules) throws DefinitionOverrideException {
        koin.loadModules(modules);
    }

    /**
     * Load properties from Map
     *
     * @param values
     */
    public KoinApplication properties(Map<String, String> values) {
        Map<String, Object> temp = new HashMap<>();
        values.forEach(new BiConsumer<String, String>() {
            @Override
            public void accept(String key, String value) {
                temp.put(key, value);
            }
        });
        koin.getPropertyRegistry().saveProperties(temp);
        return this;
    }

    /**
     * Set Koin Logger
     *
     * @param logger - logger
     */
    public KoinApplication logger(Logger logger) {
        koin.setupLogger(logger);
        return this;
    }

    /**
     * Set Koin to use [PrintLogger], by default at [Level.INFO]
     */
    public KoinApplication printLogger(Level level) {
        koin.setupLogger(KoinPlatformTools.INSTANCE().defaultLogger(level));
        return this;
    }

    /**
     * Set Koin to use [PrintLogger], by default at [Level.INFO]
     */
    public KoinApplication printLogger() {
        koin.setupLogger(KoinPlatformTools.INSTANCE().defaultLogger(Level.INFO));
        return this;
    }

    public KoinApplication createEagerInstances() {
        if (koin.getLogger().isAt(Level.DEBUG)) {
            double start = System.nanoTime() / 1000000.0;
            koin.createEagerInstances();
            double end = System.nanoTime() / 1000000.0;
            DecimalFormat to = new DecimalFormat("0.0000");
            koin.getLogger().debug("instances started in " + to.format(end - start) + " ms");
        } else {
            koin.createEagerInstances();
        }
        return this;
    }

    public void close() {
        koin.close();
    }

    public void unloadModules(Module module) {
        koin.getScopeRegistry().unloadModules(module);
    }

    public void unloadModules(List<Module> modules) {
        koin.getScopeRegistry().unloadModules(modules);
    }

    public Koin getKoin() {
        return koin;
    }
}
